﻿using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using UIOC.CodeAnalysis.Diagnostics;
using UIOC.CodeAnalysis.Extensions;
using UIOC.CodeAnalysis.Helpers;

namespace UIOC.CodeAnalysis.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class ImplementAttributeAnalyzer : DiagnosticAnalyzer {

    private static void LogInfo(string message) {
        SourceOutputHelper.LogInfo(DiagnosticSeverity.Info, $"[{nameof(ImplementAttributeAnalyzer)}] {message}");
    }

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
        => ImmutableArray.Create(
            ImplementAttributeImplementationDeclarationDiagnostic.Rule,
            ImplementAttributeNonAbstractMemberDeclarationDiagnostic.Rule,
            ImplementAttributeImplementationMissingInterfaceDiagnostic.Rule,
            ImplementAttributeParameterMustBeInterfaceDiagnostic.Rule,
            ImplementAttributeMissingObjectInterfaceDiagnostic.Rule
        );

    public override void Initialize(AnalysisContext context) {
        context.EnableConcurrentExecution();
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
        context.RegisterSyntaxNodeAction(AnalyzeClassDeclaration, SyntaxKind.ClassDeclaration);
    }

    private void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) {
        var compilation = context.Compilation;
        var assembly = compilation.Assembly;
        if (assembly.IsIgnore()) {
            return;
        }

        if (context.Node is not ClassDeclarationSyntax classDeclarationSyntax) {
            return;
        }

        var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax);
        if (classSymbol == null)
            return;

        var implementAttributeSymbol = compilation.GetTypeByMetadataName(AttributeFullNames.Implement);
        if (implementAttributeSymbol == null) {
            return;
        }

        var objectInterfaceSymbol = compilation.GetTypeByMetadataName(TypeFullNames.IObject);
        if (objectInterfaceSymbol == null) {
            return;
        }

        var implementAttributeDatas = classSymbol.GetAttributes(implementAttributeSymbol);
        if (!implementAttributeDatas.Any()) {
            return;
        }

        if (classSymbol.IsAbstract) {
            context.ReportDiagnostic(Diagnostic.Create(
                ImplementAttributeImplementationDeclarationDiagnostic.Rule,
                classDeclarationSyntax.Identifier.GetLocation(),
                classDeclarationSyntax.GetDeclaraionName(),
                "不是",
                "abstruct"
            ));
        }

        if (!classDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) {
            context.ReportDiagnostic(Diagnostic.Create(
                ImplementAttributeImplementationDeclarationDiagnostic.Rule,
                classDeclarationSyntax.Identifier.GetLocation(),
                classDeclarationSyntax.GetDeclaraionName(),
                "是",
                "partial"
            ));
        }

        if (classSymbol.IsStatic) {
            context.ReportDiagnostic(Diagnostic.Create(
                ImplementAttributeImplementationDeclarationDiagnostic.Rule,
                classDeclarationSyntax.Identifier.GetLocation(),
                classDeclarationSyntax.GetDeclaraionName(),
                "不是",
                "static"
            ));
        }

        foreach (var implementAttributeData in implementAttributeDatas) {
            if (implementAttributeData.ConstructorArguments.Length > 0 && implementAttributeData.ConstructorArguments[0].Value is INamedTypeSymbol interfaceSymbol) {
                Validate(context, implementAttributeData, classSymbol, classDeclarationSyntax, interfaceSymbol);
            }
        }

        if (implementAttributeDatas.Any() && !classSymbol.ContainsInterface(objectInterfaceSymbol)) {
            context.ReportDiagnostic(Diagnostic.Create(
                ImplementAttributeMissingObjectInterfaceDiagnostic.Rule,
                classDeclarationSyntax.Identifier.GetLocation(),
                classDeclarationSyntax.GetDeclaraionName()
            ));
        }
    }

    private void Validate(
        SyntaxNodeAnalysisContext context,
        AttributeData implementAttributeData,
        INamedTypeSymbol classSymbol,
        ClassDeclarationSyntax classDeclarationSyntax,
        INamedTypeSymbol interfaceSymbol
    ) {
        if (interfaceSymbol.TypeKind == TypeKind.Interface) {
            if (!classSymbol.ContainsInterface(interfaceSymbol)) {
                var properties = ImmutableDictionary<string, string>.Empty;
                properties = properties.Add("interfaceType", interfaceSymbol.CreateSimpleNameSyntax().ToFullString());
                context.ReportDiagnostic(Diagnostic.Create(
                    ImplementAttributeImplementationMissingInterfaceDiagnostic.Rule,
                    classDeclarationSyntax.Identifier.GetLocation(),
                    properties,
                    classDeclarationSyntax.GetDeclaraionName(),
                    interfaceSymbol.GetShortName()
                ));
            }
        } else {
            var attributeSyntax = implementAttributeData.ApplicationSyntaxReference?.GetSyntax() as AttributeSyntax;
            var location = attributeSyntax?.GetLocation() ?? classDeclarationSyntax.Identifier.GetLocation();
            context.ReportDiagnostic(Diagnostic.Create(
                ImplementAttributeParameterMustBeInterfaceDiagnostic.Rule,
                location,
                interfaceSymbol.GetShortName()
            ));
        }
    }
}